if ( !window.wala )
  window.wala = { };

/*******************************************************
 *
 * Support for operators
 *
 *******************************************************/

/**
 * Implements the a[index] operator.
 */
wala.index = function( arr, i )
{
  if ( !arr )
	return null;
  if ( i < 0 )
	i = arr.length + i;
  return arr[i];
};

/**
 * Implements the a[left..right] operator.
 */
wala.range = function( arr, left, right )
{
  if ( !arr )
	return [];
  var result = [ ];
  if ( right < 0 )
	right = arr.length - right;
  for( var i = left; i <= right; ++i )
	result.push( arr[i] );
  return result;
};

/**
 * Implements the a[left..] operator.
 */
wala.rangeMid = function( arr, left )
{
  if ( !arr )
	return [];
  var result = [ ];
  var right = arr.length - 1;
  for( var i = left; i <= right; ++i )
	result.push( arr[i] );
  return result;
};

/**
 * Implements the a[..right] operator.
 */
wala.rangeRight = function( arr, right )
{
  if ( !arr )
	return [];
  var result = [ ];
  if ( right < 0 )
	right = arr.length - right;
  for( var i = 0; i <= right; ++i )
	result.push( arr[i] );
  return result;
};

/**
 * Implements the '|' operator.
 */
wala.filter = function( list, lambda )
{
  var result = [ ];
  for( var i = 0; i < list.length; ++i )
	result.push( lambda( list[i] ) );
  return result;
};

/**
 * Implements the "." operator.
 */
wala.getValueAndListen = function( element, attrib, expr )
{
  if ( !element )
	return null;
  return element.getValueAndListen(attrib, expr);
};

/*******************************************************
 *
 * Global functions
 *
 *******************************************************/

/**
 * Implements the len() function
 */
wala.len = function( arg )
{
  if ( !arg )
	return 0;
  return arg.length;
};

wala.children = function(node)
{
  var result = [ ];
  if ( !node )
	return result;
  node.rootNode().getEvent("change").addListenerOnce( this.setDirty, this );
  var p = node.firstChild;
  while( p )
  {
	result.push(p);
	p = p.nextSibling;
  }
  return result;
};

wala.text = function(d)
{
  if ( !d )
	return "";
  d.getEvent("textChange").addListenerOnce( this.setDirty, this );
  return d.getText();
};

wala.attributes = function(d)
{
  if ( !d )
	return { };
  d.getEvent("attributeChange").addListenerOnce( this.setDirty, this );
  return d.attributes;
};

wala.localAttribute = function(d, attrib)
{
  if ( !d )
	return null;
  d.getEvent("attributeChange").addListenerOnce( this.setDirty, this );
  return d.getLocalAttribute(attrib);
};

wala.setLocalAttribute = function(d, attrib, value)
{
  if ( !d )
	return;
  return d.setLocalAttribute(attrib, value);
};

/*******************************************************
 *
 * Variables
 *
 * Functions for accessing global variables by their name.
 *
 *******************************************************/

/**
 * Map of all global variables
 */
wala.vars = { };
/**
 * A map of listeners. For each global variable name the map can contain a list of listening expressions.
 * Listeners must be instances of the Expression class.
 */
wala.var_listeners = { };

/**
 * @param key is the name of a global variable.
 */
wala.setVar = function( key, value )
{
  if ( wala.vars[key] == value )
	return;
  
  wala.vars[key] = value;
  
  var lst = wala.var_listeners[key];
  if ( lst )
  {
	for( var i = 0; i < lst.length; ++i )
	  lst[i].setDirty();
  }
};

/**
 * @param key is the name of a global variable.
 */
wala.getVar = function( key )
{
  return wala.vars[key];
};

/**
 * @param key is the name of a global variable.
 * @param expr must be instances of the Expression class. If the variable changes,
 *             the expression will be set dirty.
 */
wala.getVarAndListen = function( key, expr )
{  
  if ( !wala.var_listeners[key] )
	wala.var_listeners[key] = [ ];
  if ( wala.var_listeners[key].indexOf( expr ) == -1 )
  {
	wala.var_listeners[key].push( expr );
	if ( !expr.listenToVars )
	  expr.listenToVars = [ ];
	expr.listenToVars.push(key);
  }
  return wala.vars[key];
};

wala.stopListeningToVar = function( key, expr )
{
  wala.var_listeners[key] = wala.var_listeners[key].filter( function(e) { return e != expr; } );
};

/*******************************************************
 *
 * Expression
 *
 * In Wala expressions are first class objects. They are
 * encapsulated by an instance of the Expression class.
 * Each expression can have a list of child expressions which become
 * dirty when the parent expression changes its value.
 * The set of all expressions form a directed acyclic forest.
 *
 *******************************************************/

/**
 * @param templateInstance may be null if the expression is used in a global variable.
 *                        Usually it contains a template instance. Alternatively it can be a component instance.
 *                        In general it is the owner of the of the expression.
 * @param the function to call for evaluating the expression.
 * @param an optional list of expressions on which this expression depends.
 */
wala.Expression = function(templateInstance, func, parentExpressions)
{
  this.templateInstance = templateInstance;
  if ( this.templateInstance )
	templateInstance.__expressions.push(this);
  this.func = func;
  this.dirty = true;
  this.onChange = [ ];
  this.childExpressions = [ ];
  if ( parentExpressions )
  {
	for( var i = 0; i < parentExpressions.length; ++i )
	  parentExpressions[i].childExpressions.push(this);
  }
  this.parentExpressions = parentExpressions;
};

/**
 * @internal
 * Marks that the dirty timer has already been started to update dirty expressions.
 */
wala.Expression.dirty = false;
/**
 * @internal
 */
wala.Expression.dirtyExpressions = [];

/**
 * Starts a timer and adds the expression to the list of dirty expressions.
 */
wala.Expression.setDirty = function(expr)
{
  wala.Expression.dirtyExpressions.push(expr);
  if ( wala.Expression.dirty )
	return;
  wala.Expression.dirty = true;
  window.setTimeout( function() { wala.Expression.update(); }, 1 );
};

/**
 * Invoked by the timer. Updates all dirty expressions.
 */
wala.Expression.update = function()
{
  wala.Expression.dirty = false;
  var dirt = wala.Expression.dirtyExpressions;
  wala.Expression.dirtyExpressions = [];
  for( var i = 0; i < dirt.length; ++i )
	dirt[i].update();  
};

/**
 * Destructor
 */
wala.Expression.prototype.dtor = function()
{
  if ( this.listenToVars )
  {
	for( var i = 0; i < this.listenToVars.length; i++ )
	  wala.stopListeningToVar( this.listenToVars[i], this );
	delete this.stopListeningToVar;
  }
  
  if ( this.listenToEvents )
  {
	for( var i = 0; i < this.listenToEvents.length; i++ )
	  this.listenToEvents[i].removeExprListener( this );
	delete this.listenToEvents;
  }
  
  if ( this.parentExpressions )
  {
	for( var i = 0; i < this.parentExpressions.length; ++i )
	  this.parentExpressions[i].removeChildExpression(this);
	delete this.parentExpressions;
  }
};

wala.Expression.prototype.removeChildExpression = function(expr)
{
  var index = this.childExpressions.indexOf(expr);
  if ( index != -1 )
	this.childExpressions.slice(index, 1);
};

wala.Expression.prototype.addChildExpression = function(expr)
{
  if ( this.childExpressions.indexOf(expr) == -1 )
  {
	this.childExpressions.push(expr);
	if ( expr.parentExpressions )
	  expr.parentExpressions.push(this);
	else
	  expr.parentExpressions = [this];
  }
};

/**
 * @return the value of the expression. Computes the value if dirty. Otherwise returns the cached value.
 */
wala.Expression.prototype.evaluate = function()
{
  if ( this.dirty )
  {
	this.dirty = false;
	this.value = this.func();
  }
  return this.value;
};

wala.Expression.prototype.setDirty = function()
{
  if ( this.dirty )
	return;
  this.dirty = true;
  wala.Expression.setDirty(this);
};

wala.Expression.prototype.update = function(force)
{
  if ( !force && !this.dirty )
	return;
  
  // Remember the old value
  var val = this.value;
  // Compute the new value
  this.dirty = true;
  this.evaluate();
  // Perform some tests to see if the old and the new value are same.
  // It is ok if the test for equality yields some false negatives, but it hinders performance if it happens too often.
  if ( val != this.value )
  {
	// Perhaps the function evaluated to an array. Check if both arrays have the same content
    if ( this.value instanceof Array )
    {
	  if ( val instanceof Array )
	  {
	    if ( val.length == this.value.length )
	    {
		  var same = true;
		  for( var i = 0; i < val.length; ++i )
		  {
			if ( val[i] != this.value[i] )
			{
			  same = false;
			  break;
			}
		  }
		  if ( same )
			return;
		}
	  }
	}
    
	// Inform all listeners that the expression has changed
	for( var i = 0; i < this.onChange.length; ++i )
	  this.onChange[i].call(this);
	for( var i = 0; i < this.childExpressions.length; ++i )
	  this.childExpressions[i].update(true);
  }
};

/*******************************************************
 *
 * Handler
 *
 * Sometimes expressions are evaluated in response to some event,
 * i.e. mouse click, etc. Instead of using the Expression class, we
 * can use the Handler class here which is a bit less heavy on memory.
 *
 * The major difference is that a handler cannot have any child expressions
 * depending on it and it cannot become dirty.
 *
 *******************************************************/

/**
 * @constructor
 */
wala.Handler = function(templateInstance, func)
{
  this.templateInstance = templateInstance;
  this.evaluate = func;
  this.onChange = [ ];
  this.expressions = [ ];
};

wala.Handler.prototype.setDirty = function()
{
  // Does nothing by intention
};

wala.Handler.prototype.update = function()
{
  // Does nothing by intention
};

/*******************************************************
 *
 * Event
 *
 * This class helps in implementing events of Wala components.
 *
 *******************************************************/

/**
 * @constructor
 */
wala.Event = function(owner)
{
  this.counter = 0;
};

wala.Event.prototype.addListenerOnce = function( func, obj )
{
  var key = this.hasListener( func, obj );
  if ( key )
	return key;
  return this.addListener( func, obj );
};

/**
 * Registers an event listener.
 *
 * @param {Function} func is a function which takes no.
 * @param {Object} obj is the object on which the function func will be invoked.
 * @return {String} a key that is required for unregistering the listener.
 */
wala.Event.prototype.addListener = function( func, obj, extraArgs )
{
  if ( !func )
  {
	var x = 0;
	var y = 0;
	window.console.log( "No function specified" );
  }
  var key = "+" + (this.counter++).toString();
  this[ key ] = [ func, obj, extraArgs ];
  return key;
};

wala.Event.prototype.hasListener = function( func, obj, extraArgs )
{
  for( var key in this )
  {
    if ( key[0] == "+" )
    {
      var val = this[key];
	  if ( val[0] == func && val[1] == obj && val[2] == extraArgs )
		return key;
	}
  }
  return null;
};

wala.Event.prototype.removeListener = function( id )
{
  delete this[id];
};

/**
 * Sends the event with the specified event arguments to all registered listeners.
 */
wala.Event.prototype.emit = function(args)
{
  for( var key in this )
  {
    if ( key[0] == "+" )
    {
      var val = this[key];
      val[0].call( val[1] ? val[1] : window, args );
    }
  }
};
